package redis

import (
	"context"
	"gitee.com/aesoper/cache/factory"
	"github.com/go-redis/redis/v8"
	"sync"
	"time"
)

type (
	Cache struct {
		client *redis.Client
		cfg    Options
		waiter sync.WaitGroup
		mux    sync.RWMutex
	}

	Options struct {
		Addr       string `mapstructure:"addr" json:"addr"`
		Password   string `mapstructure:"password" json:"password"`
		MaxRetries int    `mapstructure:"maxRetries" json:"maxRetries"`
		PoolSize   int    `mapstructure:"poolSize" json:"poolSize"`
	}
)

func init() {
	factory.Register("redis", NewRedisCache)
}

func NewRedisCache() factory.Cache {
	return &Cache{}
}

func WithRedisOptions(cfg Options) factory.CacheOption {
	return func(c factory.Cache) {
		if cache, ok := c.(*Cache); ok {
			cache.cfg = cfg
		}
	}
}

func (c *Cache) StartAndConfigure(ops ...factory.CacheOption) error {
	c.cfg = Options{
		Addr:       "localhost:6379",
		Password:   "",
		MaxRetries: 0,
		PoolSize:   0,
	}
	for _, op := range ops {
		op(c)
	}

	return c.init()
}

func (c *Cache) Get(key string) ([]byte, error) {
	if err := c.init(); err != nil {
		return nil, err
	}

	c.mux.RLock()
	defer c.mux.RUnlock()

	return c.client.Get(context.Background(), key).Bytes()
}


func (c *Cache) Put(key string, val interface{}, timeout time.Duration) error {
	if err := c.init(); err != nil {
		return err
	}
	c.mux.Lock()
	defer c.mux.Unlock()
	return c.client.Set(context.Background(), key, val, timeout).Err()
}

func (c *Cache) Delete(key string) error {
	if err := c.init(); err != nil {
		return err
	}
	c.mux.Lock()
	defer c.mux.Unlock()
	return c.client.Del(context.Background(), key).Err()
}

func (c *Cache) Incr(key string) error {
	if err := c.init(); err != nil {
		return err
	}
	c.mux.Lock()
	defer c.mux.Unlock()
	return c.client.Incr(context.Background(), key).Err()
}

func (c *Cache) Decr(key string) error {
	if err := c.init(); err != nil {
		return err
	}
	return c.client.Decr(context.Background(), key).Err()
}

func (c *Cache) IsExist(key string) bool {
	if err := c.init(); err != nil {
		return false
	}
	err := c.client.Exists(context.Background(), key).Err()
	return err == nil
}

func (c *Cache) ClearAll() error {
	if err := c.init(); err != nil {
		return err
	}
	c.mux.Lock()
	defer c.mux.Unlock()
	keys, err := c.client.Keys(context.Background(), "*").Result()
	if err != nil {
		return err
	}

	if len(keys) > 0 {
		c.waiter.Add(len(keys))
		for _, key := range keys {
			go func(k string) {
				defer c.waiter.Done()
				_ = c.Delete(k)
			}(key)
		}
		c.waiter.Wait()
	}

	return nil
}


func (c *Cache) Close() error {
	if c.client != nil {
		return c.client.Close()
	}
	return nil
}

func (c *Cache) init() error {
	if c.client == nil {
		c.client = redis.NewClient(&redis.Options{
			Addr:       c.cfg.Addr,
			Password:   c.cfg.Password,
			MaxRetries: c.cfg.MaxRetries,
			PoolSize:   c.cfg.PoolSize,
			TLSConfig:  nil,
		})

		return c.client.Ping(context.Background()).Err()
	}

	return nil
}
